import { DATA } from '../symbol';
import type { Form } from './form';

export type FormMode = 'edit' | 'view' | 'design';

export type Problem = {
	type: 'warning' | 'error';
	message: string;
};

type FormFunc<R> = (value: unknown, formValue: FormValue) => R;

export interface FormControlDefinition {
	readonly type: string;
	key?: string;
	value?: unknown;
	hidden?: FormFunc<boolean> | boolean;
	readonly?: FormFunc<boolean> | boolean;
	disabled?: FormFunc<boolean> | boolean;
	formula?: FormFunc<unknown>;
}

export interface FormValue {
	getValue(key: string): unknown;
	get keys(): readonly string[];
}

export enum Capability {
	None = 0,
	/**
	 * Indicates whether a control supports the `hidden` property.
	 * If a control supports the `hidden` property and `hidden` is set to `true`,
	 * the control will be hidden in both edit and view mode,
	 * but its value can still be included in form's value.
	 */
	Hidden = 1 << 0,
	/**
	 * Indicates whether a control supports the `readonly` property.
	 * If a control supports the `readonly` property and `readonly` expression calculation result is `true`,
	 * the control will be rendered in readonly mode in form edit mode.
	 */
	Readonly = 1 << 1,
	/**
	 * Indicates whether a control supports the `disabled` property.
	 * If a control supports the `disabled` property and `disabled` expression calculation result is `true`,
	 * the control will be hidden in both edit and view modes,
	 * and its value will be removed from the form's value.
	 */
	Disabled = 1 << 2,
	/**
	 * Indicates whether a control supports the `formula` property.
	 * If a control supports the `formula` property and `formula` is set to a non-empty string or a lambda expression,
	 * the control's value will be calculated based on the formula.
	 */
	Formula = 1 << 3,
}

export type RenderState = {
	readonly: boolean;
	disabled: boolean;
	value: unknown;
	mode: FormMode;
};

export type MenuItem =
	| {
			icon?: string;
			key: string;
			text: string;
			children?: MenuItem[];
			onClick?: (menuItem: MenuItem) => void;
	  }
	| {
			type: 'caption';
			text: string;
	  }
	| {
			type: 'line';
	  };

export abstract class FormControl<Def extends FormControlDefinition> {
	private [DATA]: {
		form: Form;
		def: Def;
		val: unknown;
	};
	constructor(form: Form, def: Def) {
		this[DATA] = {
			form,
			def,
			val: undefined,
		};
	}
	get capability(): Capability {
		return Capability.Hidden | Capability.Disabled | Capability.Readonly;
	}
	get definition(): Def {
		return this[DATA].def;
	}
	get type(): string {
		return this[DATA].def.type;
	}
	setDefinition(def: Def): this {
		this[DATA].def = def;
		return this;
	}
	get value(): unknown {
		return this[DATA].val;
	}
	setValue(val: unknown) {
		this[DATA].val = val;
	}
	abstract get menu(): MenuItem[];
	/**
	 * Control should provide a properties edit panel to adjust control properties
	 */
	abstract renderProps(): Node | void;
	abstract render(state: RenderState): Node;
	abstract verify(): Problem[];
	abstract panels(): { name: string; panel: Node }[];
}
